package com.rxx.ztheme

import javassist.ClassPool
import javassist.CtClass
import javassist.CtConstructor
import org.apache.commons.io.FileUtils
/**
 * @author 冉超群
 * @date 2017/12/5-17:32
 * @desc https://www.2cto.com/kf/201604/499386.html
 */
public class InjectUtils {


    private ClassPool pool = new ClassPool(true)

    private List<String> libPaths = new ArrayList<>()

    /**
     * 添加classPath到ClassPool
     * @param libPath
     */
    public  void appendClassPath(String libPath) {
        libPaths.add(libPath)
        pool.appendClassPath(libPath)
    }

    /**
     * 遍历该目录下的所有class，对所有class进行代码注入。
     * 其中以下class是不需要注入代码的：
     * --- 1. R文件相关
     * --- 2. 配置文件相关（BuildConfig）
     * --- 3. Application
     * @param path 目录的路径
     */
    public  void injectDir(String path, List<String> ignores) {
        File dir = new File(path)
        if (dir.isDirectory()) {
            dir.eachFileRecurse { File file ->
                if (file.isDirectory()) {
                    return true
                }
                if (!file.getAbsolutePath().endsWith('.class')) {
                    return true
                }
                int end = file.absolutePath.length() - 6 // .class = 6
                int index = path.length() + 1 // has .
                String className = file.absolutePath.substring(index, end).replace(File.separator, ".")
                if (className.contains('R$')
                        || className.contains('R.class')
                        || className.contains('BuildConfig.class')) {
                    System.out.println("injectDir filePath R ignore:" + className)
                    return true
                }
                boolean isIgnore = false
                ignores.find { String ignore ->
                    if (className.contains(ignore)) {
                        System.out.println("injectDir filePath user add ignore:" + className)
                        isIgnore = true
                        return true
                    }
                }
                if (isIgnore) {
                    return true
                }
                injectClass(className, path)
            }
        }
    }

    /**
     * 这里需要将jar包先解压，注入代码后再重新生成jar包
     * @path jar包的绝对路径
     */
    public  void injectJar(String path) {
        if (path.endsWith(".jar")) {
            File jarFile = new File(path)

            // jar包解压后的保存路径
            String jarZipDir = jarFile.getParent() + "/" + jarFile.getName().replace('.jar', '')

            // 解压jar包, 返回jar包中所有class的完整类名的集合（带.class后缀）
            List classNameList = JarZipUtil.unzipJar(path, jarZipDir)

            // 删除原来的jar包
            jarFile.delete()

            for (String className : classNameList) {
                if (className.endsWith(".class")
                        && !className.contains('R$')
                        && !className.contains('R.class')
                        && !className.contains("BuildConfig.class")) {
                    className = className.substring(0, className.length() - 6)
                    injectClass(className, jarZipDir)
                } else {
                    System.out.println("inject class ignore:" + className)
                }
            }
            JarZipUtil.zipJar(jarZipDir, path)
            // 删除目录
            FileUtils.deleteDirectory(new File(jarZipDir))
        }
    }

    private  void injectClass(String className, String path) {
        CtClass c = pool.getCtClass(className)
        if (c.isFrozen()) {
            c.defrost()
        }
        try {
            CtConstructor unCtConstructor = c.getDeclaredConstructors()[0]
            if (unCtConstructor != null) {
                unCtConstructor.insertAfter(Config.INJECT_CONTENT)
            }
        } catch (Exception ex) {
            System.out.println("err inject to " + className + ",reason :" + ex.toString())
        }
        c.writeFile(path)
    }


}
